package com.xlongwei.accounts.config;

import java.lang.reflect.Type;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.sql.DataSource;
import org.noear.solon.Solon;
import org.noear.solon.Utils;
import org.noear.solon.annotation.Bean;
import org.noear.solon.annotation.Configuration;
import org.noear.solon.core.util.ClassUtil;
import org.noear.solon.data.cache.CacheService;
import org.noear.solon.data.cache.EmptyCacheService;
import org.noear.solon.data.cache.LocalCacheService;
import org.smartboot.http.common.utils.NumberUtils;

import cn.dev33.satoken.solon.integration.SaTokenInterceptor;
import cn.dev33.satoken.stp.StpUtil;
import lombok.extern.slf4j.Slf4j;

@Slf4j
@Configuration
public class AppConfig {
    public static final String mobileDsAdmin = Solon.cfg().get("accounts.admin", "1"); // 配置管理员mobile_ds
    public static final List<String> allowNewUserDsNames = Arrays.asList(Solon.cfg().get("accounts.allowNewUserDsNames", "accounts").split("[,;]")); // 配置数据源允许自动注册
    public static final boolean allowAutoNewDs = "true".equalsIgnoreCase(Solon.cfg().get("accounts.allowAutoNewDs", "true")); // 配置允许自动注册数据源，通过checkLogin回填，不必手动输入
    public static final boolean enableH2 = Solon.cfg().getBool("accounts.enableH2", ClassUtil.loadClass("org.h2.tools.Server") != null); // 是否支持h2
    public static final boolean enableAutoServer = enableH2 && Solon.cfg().getBool("accounts.enableAutoServer", false); // 是否启用h2混合模式
    public static final boolean enableRedis = "true".equalsIgnoreCase(Solon.cfg().get("accounts.enableRedis", "false")); // 配置启用redis
    public static final String serverHostAndPort = Solon.cfg().get("accounts.serverHostAndPort"); // 分布式部署时，若启用h2则有且仅有一个server:port
    public static final boolean isClient = enableH2 && enableRedis && Utils.isNotBlank(serverHostAndPort); // isClient时需将newDs请求转发到server:port
    public static final boolean isServer = enableH2 && enableRedis && Utils.isBlank(serverHostAndPort); // isServer时newDs之后发布通知即可
    public static final String channel = Solon.cfg().get("accounts.channel","accounts"); // 分布式部署时，若启用h2则通过channel进行广播
    public static final int defSeconds = Math.max(30, NumberUtils.toInt(Solon.cfg().get("solon.cache.defSeconds"), 3600));
    public static final boolean enableCache = "true".equalsIgnoreCase(Solon.cfg().get("solon.cache.enable", "true"));
    static {
        log.info("enableRedis={} serverHostAndPort={} isClient={}", enableRedis, serverHostAndPort, isClient);
        RedisConfig.init();
        log.info("enableH2={} channel={}", enableH2, channel);
    }
    /** 先判断登录@SaCheckLogin，再调此方法 */
    public static boolean isSuperAdmin() {
        return mobileDsAdmin.equals(StpUtil.getLoginId());
    }
    /**
     * <pre>
     * 注意顺序：LoggingRouterInterceptor记日志-30 > AppRouterInterceptor捕异常-20 > SaTokenInterceptor 验登录-10
     * </pre>
     */
    @Bean(index = -10) // -100，是顺序位（低值优先）
    public SaTokenInterceptor saTokenInterceptor() {
        // 必须addInclude匹配，否则不会检查@SaCheckLogin，路径匹配但是没有注解也不会拦截/user/login，排除路径可以addExclude
        return new SaTokenInterceptor().addInclude("/**"); // 用于支持规划处理及注解处理
    }

    @Bean
    public CacheService cacheService() {
        log.info("mobileDsAdmin={} allowAutoNewDs={} allowNewUserDsNames={}", mobileDsAdmin, allowAutoNewDs, allowNewUserDsNames);
        Map<String, DataSource> map = Solon.context().getBeansMapOfType(DataSource.class);
        log.info("cache={} defSeconds={} dsNames={}", enableCache, defSeconds, map.keySet());
        return new DsCacheService(map.keySet());
    }

    public static class DsCacheService extends EmptyCacheService {
        private Map<String, CacheService> caches = new HashMap<>();
        static final CacheService defCacheService = enableCache ? (enableRedis ? RedisConfig.cacheService(null) : new LocalCacheService(defSeconds)) : new EmptyCacheService();

        public DsCacheService(Set<String> dsNames) {
            for (String name : dsNames) {
                if ("accounts".equals(name))
                    continue;
                add(name);
            }
        }

        public void add(String mobile) {
            caches.computeIfAbsent(mobile, (name) -> {
                return enableCache ? (enableRedis ? RedisConfig.cacheService(mobile) : new LocalCacheService(defSeconds)) : null;
            });
        }

        public CacheService cacheService(String dsName) {
            CacheService cacheService = Utils.isBlank(dsName) ? null : caches.get(dsName);
            return cacheService != null ? cacheService : defCacheService;
        }

        private CacheService cacheService() {
            return cacheService(MySqlUtils.dsName());
        }

        @Override
        public <T> T get(String key, Type type) {
            return enableCache ? cacheService().get(key, type) : null;
        }

        @Override
        public void remove(String key) {
            if (enableCache) cacheService().remove(key);
        }

        @Override
        public void store(String key, Object obj, int seconds) {
            if (enableCache) cacheService().store(key, obj, seconds);
        }

    }
}
